/**
  nvn-data-source.c - Created by Timothy Morey on 7/18/2012
*/

#include <string.h>

#ifdef __WINDOWS__
#pragma warning(disable:4996)
#endif

#include "client.h"
#include "err.h"
#include "logger.h"
#include "messages.h"
#include "nvn-data-source.h"
#include "server.h"

#ifndef __WINDOWS__
#define min(x, y) x < y ? x : y
#endif

int g_BaseServerPort = 26662;

typedef struct
{
  DataSource Base;
  Server* Server;
} NvnServerDataSource;

int MessageReceivedCallback(Server* server,
                            const char* clientname,
                            const char* recvbuf,
                            int recvlen,
                            char* replybuf,
                            int* replylen);
int ParseNvnServerDataset(const char* dataset, 
                          char* servername, int servernamelen,
                          char* port, int portlen,
                          char* datasetname, int datasetnamelen);


/*****************************************************************************
 * Implementation of nvn-data-source.h functions
 *****************************************************************************/

EXPORT int NvnDataSourceDisconnect(int dsid)
{
  int retval = NVN_NOERR;
  DataSource* ds;

  retval = GetRegisteredDataSource(dsid, &ds);
  if(NVN_NOERR == retval && ds && DataSourceTypeNvnServer == ds->Type)
  {
    // TODO...
    UnregisterDataSource(dsid);
  }

  return retval;
}

EXPORT int NvnDataSourceGetDatasetSchema(DataSourceDescriptor desc,
                                         DataSetSchema* schema)
{
  int retval = NVN_NOERR;
  Client* client = 0;
  char servername[256];
  int servernamelen = 256;
  char port[16];
  int portlen = 16;
  char datasetname[256];
  int datasetnamelen = 256;
  char req[1024];
  int reqlen = 1024;
  char resp[4096];
  int resplen = 4096;

  if(! schema)
    retval = NVN_EINVARGS;

  if(NVN_NOERR == retval)
  {
    memset(servername, 0, servernamelen);
    memset(port, 0, portlen);
    memset(datasetname, 0, datasetnamelen);
    retval = ParseNvnServerDataset(desc.Dataset, 
                                   servername, servernamelen,
                                   port, portlen, 
                                   datasetname, datasetnamelen);
  }

  if(NVN_NOERR == retval)
  {
    retval = ConnectClient(servername, port, &client);
  }

  if(NVN_NOERR == retval)
  {
    retval = CreateQueryDataSetSchemaRequest(datasetname, req, &reqlen);
  }

  if(NVN_NOERR == retval)
  {
    retval = SendAndReceive(client, req, reqlen, resp, &resplen);
  }

  if(NVN_NOERR == retval)
  {
    retval = ParseQueryDataSetSchemaResponse(resp, resplen, schema);
  }

  if(client)
  {
    DestroyClient(client);
    client = 0;
  }

  return retval;
}

EXPORT int NvnDataSourceGetGridSchema(DataSourceDescriptor desc,
                                      DataGridSchema* schema)
{
  int retval = NVN_NOERR;
  Client* client = 0;
  char servername[256];
  int servernamelen = 256;
  char port[16];
  int portlen = 16;
  char datasetname[256];
  int datasetnamelen = 256;
  char req[1024];
  int reqlen = 1024;
  char resp[4096];
  int resplen = 4096;

  if(! schema)
    retval = NVN_EINVARGS;

  if(NVN_NOERR == retval)
  {
    memset(servername, 0, servernamelen);
    memset(port, 0, portlen);
    memset(datasetname, 0, datasetnamelen);
    retval = ParseNvnServerDataset(desc.Dataset, 
                                   servername, servernamelen,
                                   port, portlen, 
                                   datasetname, datasetnamelen);
  }

  if(NVN_NOERR == retval)
  {
    retval = ConnectClient(servername, port, &client);
  }

  if(NVN_NOERR == retval)
  {
    retval = CreateQueryDataGridSchemaRequest(datasetname, desc.Varname, req, &reqlen);
  }

  if(NVN_NOERR == retval)
  {
    retval = SendAndReceive(client, req, reqlen, resp, &resplen);
  }

  if(NVN_NOERR == retval)
  {
    retval = ParseQueryDataGridSchemaResponse(resp, resplen, schema);
  }

  if(client)
  {
    DestroyClient(client);
    client = 0;
  }

  return retval;
}

int NvnDataSourceSetBasePort(int port)
{
	g_BaseServerPort = port;
}

int NvnDataSourceStartStream(DataSourceDescriptor desc,
                             DataFrameCallback callback,
                             int* dsid)
{
  int retval = NVN_NOERR;
  char servername[256];
  int servernamelen = 256;
  char port[16];
  int portlen = 16;
  char datasetname[256];
  int datasetnamelen = 256;
  NvnServerDataSource* ds = 0;
  Client* client;
  int msglen = 1024;
  char msg[1024];
	int ret = NVN_ERROR;
	int localport = g_BaseServerPort;

  if(!dsid)
    retval = NVN_EINVARGS;

  if(NVN_NOERR == retval)
  {
    ds = (NvnServerDataSource*)malloc(sizeof(NvnServerDataSource));
    retval = RegisterDataSource((DataSource*)ds, dsid);
  }

  if(NVN_NOERR == retval)
  {
    memset(servername, 0, servernamelen);
    memset(port, 0, portlen);
    memset(datasetname, 0, datasetnamelen);
    retval = ParseNvnServerDataset(desc.Dataset, 
                                   servername, servernamelen,
                                   port, portlen, 
                                   datasetname, datasetnamelen);
  }

  if(NVN_NOERR == retval)
  {
    memset(ds, 0, sizeof(NvnServerDataSource));
    ds->Base.Descriptor = desc;
    ds->Base.Type = DataSourceTypeNvnServer;
    ds->Base.Callback = callback;
		while(ret != NVN_NOERR)
		{
			ret = StartAsyncServer(localport, 
														 MessageReceivedCallback, 
														 ServerModeStream, 
														 &ds->Server);

			if(ret != NVN_NOERR)
			{
				localport++;

				if(localport > g_BaseServerPort + 100)
				{
					WriteMessage(LogLevelError, "Failed to find an available port");
					retval = NVN_ECONNFAIL;
					break;
				}
			}
		}
  }

  if(NVN_NOERR == retval)
  {
    retval = ConnectClient(servername, port, &client);
  }

  if(NVN_NOERR == retval)
  {
    CreateStartStreamRequest(localport, datasetname, desc.Varname, msg, &msglen);
    retval = Send(client, msg, msglen);
  }

  if(NVN_NOERR == retval)
  {
    ds->Base.ID = *dsid;
  }
  else
  {
    if(ds)
    {
      if(ds->Server)
        StopServer(ds->Server);

      free(ds);
      UnregisterDataSource(*dsid);
    }
  }

  return retval;
}


/*****************************************************************************
 * Implementation of local functions
 *****************************************************************************/

int MessageReceivedCallback(Server* server,
                            const char* clientname,
                            const char* recvbuf,
                            int recvlen,
                            char* replybuf,
                            int* replylen)
{
  int retval = NVN_NOERR;
  int i = 0;
  DataSource* ds = 0;

  for(i = 0; i < MAX_DATA_SOURCES; i++)
  {
    if(NVN_NOERR == GetRegisteredDataSource(i, &ds) && ds &&
       DataSourceTypeNvnServer == ds->Type &&
       server == ((NvnServerDataSource*)ds)->Server)
    {
      ds->Callback(i, recvbuf, recvlen);
      break;
    }
  }

  return retval;
}

int ParseNvnServerDataset(const char* dataset, 
                          char* servername, int servernamelen,
                          char* port, int portlen,
                          char* datasetname, int datasetnamelen)
{
  int retval = NVN_NOERR;
  int i = 0;
  int colonpos = -1;
  int slashpos = -1;
  int len = strlen(dataset);

  for(i = 0; i < len; i++)
  {
    if(dataset[i] == ':')
    {
      colonpos = i;
      strncpy(servername, dataset, min(servernamelen, colonpos));
    }
    else if(dataset[i] == '/' && colonpos > 0)
    {
      slashpos = i;
      strncpy(port, dataset+colonpos+1, min(portlen, slashpos-colonpos-1));
      strncpy(datasetname, dataset+slashpos+1, min(datasetnamelen, len-slashpos-1));
    }
  }

  if(colonpos <= 0 || colonpos >= slashpos || slashpos > len - 1)
    retval = NVN_EINVSTRING;

  return retval;
}
